package org.chispa.api.parsers;

import java.util.NoSuchElementException;

import org.chispa.api.domain.DomainCryptoListener;
import org.chispa.api.domain.DomainParameter;
import org.chispa.api.domain.DomainRetriever;
import org.chispa.api.domain.DomainRetrieverFactory;
import org.chispa.api.parsers.StateRepository.State;
import org.chispa.api.util.BadSyntaxException;
import org.chispa.api.util.BinningNumber;
import org.chispa.api.util.ExpressionLanguage;
import org.chispa.api.util.ExpressionLanguage.Type;
import org.chispa.api.util.NoSuitableVisitorException;

class EncryptParserState extends HashMethodParserState implements DomainCryptoListener {

	
	protected EncryptParserState(ParserState prev) {
		super(prev);
		ORIG=ExpressionLanguage.PLAIN_ENC;
		CHANGED=ExpressionLanguage.PROC_ENC;
	}
	
	@Override
	protected ParserState doValidate(Type type) throws NoSuchElementException, BadSyntaxException, NoSuitableVisitorException {
		String text=scan.next();
		if(!text.equals("("))
			throw new BadSyntaxException("Encryption method name must be followed by left bracket!");
		append(text);
		String preBuff=flush();
		if(type.equals(Type.STRING))
		{
			encString(preBuff);
		}
		else
		{
			encNumeric(preBuff,type);
		}
		prev.setBuffer(buffer);
		return prev.validateState();
	}
	
	private void encString(String preBuff) throws NoSuchElementException, BadSyntaxException, NoSuitableVisitorException
	{
		String token=scan.next();
		//Is it a literal with spaces...
		if(isUnclosedLiteral(token))
		{
			repo.getStateFor(State.LITERAL, this).validateState(token);
		}
		//Or a simple literal?
		else 
		{
			append(token);
		}
		token=ExpressionLanguage.unquote(flush());
		String orig=token;
		token=listener.onEncryptString(token);
		append(preBuff);
		append(token);
		append(ExpressionLanguage.COMMA);
		token=listener.onSimpleHash(orig);
		append(token);
		checkClosing();
	}
	
	private void encNumeric(String prebuff,Type type) throws BadSyntaxException, NoSuitableVisitorException
	{
		
		String token=scan.next();
		if(!ExpressionLanguage.isNumeric(token))
		{
			throw new BadSyntaxException("Numerical encryption function accepts one number as input!");
		}
		String[] values=prebuff.split(" ");
		if(values.length<3)
			throw new BadSyntaxException("Corrupted query format!");
		String id=values[values.length-3];
		if(!ExpressionLanguage.isObject(id))
			throw new BadSyntaxException("Objects must be of \\\"ns:name\\\" format!");
		DomainRetriever dr=DomainRetrieverFactory.getInstance(this);
		BinningNumber bin;
		if(type.equals(Type.FLOAT))
		{
			Double dValue=Double.parseDouble(token);
			bin=dr.createNumber(id, dValue);
		}
		else
		{
			Long lValue=Long.parseLong(token);
			bin=dr.createNumber(id, lValue);
		}
		String encr=listener.onEncryptNumber(bin,type);
		append(prebuff);
		append(encr);
		append(ExpressionLanguage.COMMA);
		String hash=listener.onSimpleHash(bin, type).toString();
		append(hash);
		checkClosing();
	}
	
	private void checkClosing() throws BadSyntaxException
	{
		String token=scan.next();
		if(!token.equals(")"))
			throw new BadSyntaxException("Encryption function was not closed properly");
		append(token);
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T extends Number> T onNumber(String enc, T type) {
		String dec;
		try {
			dec = listener.onDecryptText(enc);
			return (T) DomainParameter.parseNumber(dec, type.getClass());
		} catch (NoSuitableVisitorException e) {
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Integer onBucketNumber(String enc) {
		String dec;
		try {
			dec = listener.onDecryptText(enc);
			return Integer.parseInt(dec);
		} catch (NoSuitableVisitorException e) {
			e.printStackTrace();
		}
		return null;
	}

}
